package com.gitee.l0km.aocache;

import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;
import org.aspectj.lang.ProceedingJoinPoint;

class InvokeKey implements AocacheConstant{
	final Object[] args;
	/**
	 * 切入点对象，不参与hash,equals<br>
	 * NOTE:<br>
	 * 这里切入点对象使用弱引用类型，目的是为避免对target对象的强引用(参见{@link ProceedingJoinPoint#getTarget()}), 
	 * 同时，强引用会导致对 {@link MemberCache.TargetCache}的弱引用KEY策略无效,
	 * 参见 {@link MemberCache.NonStaticMemberCache#apply(java.lang.reflect.Member)}<br>
	 * 最终减少不必要的内存占用<br>
	 * 另外要说明的是:<br>
	 * 使用弱引用类型并不会影响 {@link InvokeCache#apply(InvokeKey)}计算调用执行，<br>
	 * 因为计算调用执行在
	 * {@link MemberCache#around(ProceedingJoinPoint, com.gitee.l0km.aocache.config.CacheConfigSupplier)}环境
	 * 中执行，此时joinPoint作为 {@code MemberCache#around}方法的输入参数，为强引用。<br>
	 * 所以可以确保在计算调用执行时joinPoint不会被JVM回收。
	 */
	private WeakReference<ProceedingJoinPoint> joinPoint;
	/**
	 * args是否被复制标志,不参与hash,equals
	 */
	volatile boolean argWrapped;
	/**
	 * 计算(原方法)调用计数器,不参与hash,equals
	 */
	final AtomicLong proceedCounter = new AtomicLong();
	/**
	 * 计算(原方法)调用原始执行时间总计(不包含缓存开销),不参与hash,equals
	 */
	final AtomicLong proceedRealCost = new AtomicLong();
	/**
	 * 切面方法调用计数器,不参与hash,equals
	 */
	final AtomicLong aroundCounter;
	
	/**
	 * 通过ProceedingJoinPoint构造InvokeKey
	 * @param joinPoint 切入点对象
	 */
	InvokeKey(ProceedingJoinPoint joinPoint) {
		this.args = joinPoint.getArgs();
		this.joinPoint = new WeakReference<>(joinPoint);
		this.aroundCounter = new AtomicLong();
	}

    /**
     * 通过target和args构造InvokeKey
     * <p>
     * 此构造方法主要用于缓存更新机制，允许在没有ProceedingJoinPoint的情况下构造InvokeKey
     * 注意：使用此构造方法时joinPoint为null，这意味着该InvokeKey实例不能用于执行实际的方法调用
     * @param args 方法参数数组
     */
    InvokeKey(Object[] args) {
        this.args = args != null ? args : EMPTY_OBJECT_ARRAY;
        this.joinPoint = new WeakReference<>(null);
		this.aroundCounter = new AtomicLong();
    }

	ProceedingJoinPoint getJoinPoint() {
		return joinPoint.get();

	}
	/**
	 * 重置对象状态，为下一次查找做准备。<p>
	 * 此方法会将切面方法调用计数器重置为 0，并返回当前对象实例。<br>
	 * 这样在进行下一次查找时，计数器保持最小值 ，
	 * 避免在比较相等时调用{@link #syncMaxCounters(AtomicLong)}时计算出不正确的较大值同步到缓存key 。<br>
	 * NOTE: 如果使用当前对象执行多次缓存查找，需要调用此方法重置对象状态。<br>
	 */
	InvokeKey resetForNextLookup() {
		this.aroundCounter.set(0);
		return this;
	}
	@Override
	public int hashCode() {
		return Arrays.deepHashCode(args);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		InvokeKey other = (InvokeKey) obj;
		boolean eq = Arrays.deepEquals(args, other.args);
		if(eq){
			/** 
			 * 在比较相等时将同步两个对象的切面方法调用计数器数值,
			 * 确保相等的对象的切面方法调用计数器拥有相同的值,
			 * 这样就将在InvokeCache中内部key中的aroundCounter
			 * 传递给了用于查找的外部key，比如：
			 * MemberCache.around方法中创建一个InvokeKey对象key1,
			 * 用key1在InvokeCache找到key2。找到后，key2的aroundCounter赋值给key1的aroundCounter,
			 * 这样key1和key2的aroundCounter就相等了。
			 * 这样 MemberCache中执行aroundCounter.getAndIncrement()时才能正确递增aroundCounter。
			 * 且保存到缓存中
			 */
			syncMaxCounters(other.aroundCounter);
			/** 
			 * 同样的道理，
			 * 在比较相等时将非空切入点对象赋值给非空切入点对象
			 * 确保相等的对象拥有相同的切入点对象, 这样就将在InvokeCache中内部key中的joinPoint
			 * 传递给了用于查找的外部key
			 * NOTE:other为缓存中的对象
			 * 参见 com.google.common.cache.LocalCache.Segment.getEntry(Object key, int hash),
			 * 以及 com.google.common.base.Equivalence.equivalent(Object a, Object b)
			 */
			ProceedingJoinPoint jp= other.joinPoint.get();
			if (null != jp) {
				joinPoint = other.joinPoint;
			} else if (null != (jp = joinPoint.get())) {
				other.joinPoint = joinPoint;
			}
			jp = null;
		}
		return eq;
	}

	/**
	 * 累积最大值到另一个计数器
	 * 此方法确保两个计数器中的最大值被累积到另一个计数器中，避免数据丢失
	 * 
	 * @param other 另一个需要累积最大值的计数器
	 */
	private void syncMaxCounters(AtomicLong other) {
	    long selfPrev, otherPrev;
	    for (;;) {
	        selfPrev = aroundCounter.get();
	        otherPrev = other.get();
	        // 如果两者值相等，无需更新，退出循环
	        if (selfPrev == otherPrev) {
	            break;
	        // 如果当前对象的计数器值大于另一个计数器的值
	        } else if (selfPrev > otherPrev) {
	            // 尝试将另一个计数器的值更新为当前对象的计数器值
	            if (other.compareAndSet(otherPrev, selfPrev)) {
	                break;
	            }
	        } else {
	            // 如果另一个计数器的值大于当前对象的计数器值
	            // 尝试将当前对象的计数器值更新为另一个计数器的值
	            if (aroundCounter.compareAndSet(selfPrev, otherPrev)) {
	                break;
	            }
	        }
	    }
	}
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("InvokeKey [args=").append(Arrays.deepToString(args))
				.append(", proceedCounter=").append(proceedCounter)
				.append(", aroundCounter=").append(aroundCounter).append("]");
		return builder.toString();
	}
	public String toSimpleString() {
		StringBuilder builder = new StringBuilder();
		builder.append("[args=").append(Arrays.deepToString(args))
		.append("]");
		return builder.toString();
	}
}